package com.basic.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.basic.exception.GlobalException;
import com.basic.service.MyGenService;
import com.basic.service.dto.GenParam;
import com.basic.service.vo.DataBasesParam;
import com.basic.utils.GenUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.runtime.RuntimeConstants;
import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@Service
@Slf4j
public class MyGenServiceImpl implements MyGenService {

    private static VelocityEngine velocityEngine;

    static {
        velocityEngine = new VelocityEngine();
        velocityEngine.setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath");
        velocityEngine.setProperty("classpath.resource.loader.class", ClasspathResourceLoader.class.getName());
        velocityEngine.init();
    }

    @Override
    public StringWriter genCodeForVelocityContext(GenParam genParam) {
        if (ObjectUtil.isNull(genParam) || ObjectUtil.isNull(genParam.getType())) {
            throw new GlobalException("传入参数错误");
        }

        final GenParam.Type type = GenParam.Type.valueOf(genParam.getType());
        if (ObjectUtil.isNull(type)) {
            throw new GlobalException("传入不支持的类型");
        }

        final Template template = velocityEngine.getTemplate(type.getPath());

        try {
            final VelocityContext velocityContext = GenUtil.createVelocityContextForClass(genParam);
            final StringWriter stringWriter = new StringWriter();
            template.merge(velocityContext, stringWriter);
            return stringWriter;
        } catch (IllegalAccessException e) {
            log.error(e.toString());
            throw new GlobalException("解析失败");
        }
    }

    @Override
    public void genZip(ZipOutputStream zipOutputStream, String templateString, GenParam genParam) {
        if (ObjectUtil.isNull(zipOutputStream) || StrUtil.isBlank(templateString) || ObjectUtil.isNull(genParam) || ObjectUtil.isNull(genParam.getType())) {
            throw new GlobalException("参数错误");
        }

        final GenParam.Type type = GenParam.Type.valueOf(genParam.getType());
        if (ObjectUtil.isNull(type)) {
            throw new GlobalException("传入不支持的类型");
        }

        try {
            ZipEntry zipEntry = new ZipEntry(StrUtil.format(type.getFilePathTemplate(), genParam.getPackagePathFormat(), genParam.getTableInfo().getClassName()));
            zipOutputStream.putNextEntry(zipEntry);
            zipOutputStream.write(templateString.getBytes());

        } catch (IOException e) {
            log.error(e.toString());
            throw new GlobalException("生成zip文件失败");
        }
    }

    @Override
    public void genZip(ZipOutputStream zipOutputStream, StringWriter stringWriter, GenParam genParam) {
        if (ObjectUtil.isNull(zipOutputStream)) {
            throw new GlobalException("传入参数错误");
        }

        genZip(zipOutputStream, stringWriter.toString(), genParam);
    }

    @Override
    public void connectAndGetData(DataBasesParam param) {
        try (FileOutputStream fos = new FileOutputStream(param.getZipPathFormat());
             ZipOutputStream zipOutputStream = new ZipOutputStream(fos)) {
            final JdbcTemplate jdbcTemplate = GenUtil.createJdbcTemplate(param.getDriverClass(), param.getUrl(), param.getUsername(), param.getPasswd());
            Map<String, GenParam.TableInfo> allDataForDatasource;
            if (ObjectUtil.isEmpty(param.getTableNames())) {
                allDataForDatasource = GenUtil.getAllDataForDatasource(jdbcTemplate, GenUtil.getDatabaseNameForUrl(param.getUrl()));
            } else {
                allDataForDatasource = GenUtil.getAllDataForDatasource(jdbcTemplate, param.getTableNames(), GenUtil.getDatabaseNameForUrl(param.getUrl()));
            }
            final List<GenParam.TableInfo> tableInfoList = new ArrayList<>();
            List<GenParam.Type> collectOne = Arrays.stream(GenParam.Type.values()).filter(e -> e.isOne()).collect(Collectors.toList());
            List<GenParam.Type> collectMore = Arrays.stream(GenParam.Type.values()).filter(e -> !e.isOne()).collect(Collectors.toList());

            GenParam genParam = GenParam.builder()
                    .author(param.getAuthor())
                    .module(param.getModule())
                    .packagePath(param.getPackagePath())
                    .createTime(DateUtil.format(new Date(), "yyyy-MM-dd"))
                    .port(param.getPort())
                    .driverClass(param.getDriverClass())
                    .url(param.getUrl())
                    .username(param.getUsername())
                    .build();
            for (Map.Entry<String, GenParam.TableInfo> each : allDataForDatasource.entrySet()) {
                GenParam.TableInfo tableInfo = each.getValue();
                tableInfoList.add(each.getValue());
                genParam.setControllerPath("/" + tableInfo.getClassNameSmall());
                genParam.setTableInfo(tableInfo);
                genZipCodeForType(collectMore, genParam, zipOutputStream);
            }

            genParam.setTableInfoList(tableInfoList);
            genZipCodeForType(collectOne, genParam, zipOutputStream);
            zipOutputStream.closeEntry();

        } catch (Exception e) {
            e.printStackTrace();
            log.error("文件生成失败" + e.toString());
            throw new GlobalException("文件生成失败");
        }
    }

    private void genZipCodeForType(List<GenParam.Type> collect, GenParam genParam, ZipOutputStream zipOutputStream) {
        for (GenParam.Type eachType : collect) {
            genParam.setType(eachType.getVal());
            genZip(zipOutputStream, genCodeForVelocityContext(genParam).toString(), genParam);
        }
    }

}
